﻿using System;
using System.Reflection.Emit;
using Aeon.Emulator.DebugSupport;

namespace Aeon.Emulator.Decoding.Recompiler.StringCompilers
{
    /// <summary>
    /// Abstract base class for string instruction recompilers.
    /// </summary>
    internal abstract class StringBase
    {
        #region Private Fields
        private readonly RegisterExpressions registers;
        private readonly bool checkFlags;
        private ILGenerator il;
        private CompilerContext context;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the StringBase class.
        /// </summary>
        /// <param name="registers">Associated register expression generator.</param>
        /// <param name="checkFlags">Value indicating whether the zero flag is tested on each iteration.</param>
        protected StringBase(RegisterExpressions registers, bool checkFlags)
        {
            this.registers = registers;
            this.checkFlags = checkFlags;
        }
        #endregion

        #region Protected Properties
        /// <summary>
        /// Gets the instruction operand size in bits.
        /// </summary>
        protected int OperandSize { get; private set; }
        /// <summary>
        /// Gets the instruction address size in bits.
        /// </summary>
        protected int AddressSize { get; private set; }
        /// <summary>
        /// Gets the instruction repeat prefix value.
        /// </summary>
        protected RepeatPrefix Repeat { get; private set; }
        /// <summary>
        /// Gets the instruction segment override prefix value.
        /// </summary>
        protected SegmentRegister SegmentOverride { get; private set; }
        /// <summary>
        /// Gets the associated ILGenerator instance.
        /// </summary>
        protected ILGenerator IL
        {
            get { return this.il; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Generates IL to emulate the instruction.
        /// </summary>
        /// <param name="context">Instruction compiler context.</param>
        /// <param name="il">Associated ILGenerator instance.</param>
        public void Generate(CompilerContext context, ILGenerator il)
        {
            this.OperandSize = context.Instruction.Opcode.Name.EndsWith("b") ? 8 : context.OperandSize;
            this.AddressSize = context.AddressSize;
            this.Repeat = GetRepeatPrefix(context.Instruction.Prefixes);
            this.SegmentOverride = GetSegmentOverride(context.Instruction.Prefixes);
            this.il = il;
            this.context = context;

            GenerateCode();
        }
        #endregion

        /// <summary>
        /// Returns the repeat prefix in effect.
        /// </summary>
        /// <param name="prefix">Currently active prefix flags.</param>
        /// <returns>Repeat prefix value.</returns>
        private static RepeatPrefix GetRepeatPrefix(PrefixState prefix)
        {
            if((prefix & PrefixState.Repe) != 0)
                return RepeatPrefix.Repe;
            else if((prefix & PrefixState.Repne) != 0)
                return RepeatPrefix.Repne;
            else
                return RepeatPrefix.None;
        }
        /// <summary>
        /// Returns the segment override prefix in effect.
        /// </summary>
        /// <param name="prefix">Currently active prefix flags.</param>
        /// <returns>Repeat prefix value.</returns>
        private static SegmentRegister GetSegmentOverride(PrefixState prefix)
        {
            if((prefix & PrefixState.CS) != 0)
                return SegmentRegister.CS;
            else if((prefix & PrefixState.DS) != 0)
                return SegmentRegister.DS;
            else if((prefix & PrefixState.ES) != 0)
                return SegmentRegister.ES;
            else if((prefix & PrefixState.SS) != 0)
                return SegmentRegister.SS;
            else if((prefix & PrefixState.FS) != 0)
                return SegmentRegister.FS;
            else if((prefix & PrefixState.GS) != 0)
                return SegmentRegister.GS;
            else
                return SegmentRegister.Default;
        }

        #region Protected Methods
        /// <summary>
        /// Generates the IL for one iteration of the instruction.
        /// </summary>
        protected abstract void GenerateIteration();
        /// <summary>
        /// Generates IL to get the value at (DS):(E)SI.
        /// </summary>
        protected void GenerateDS_SI()
        {
            il.LoadArgument(0);
            il.Emit(OpCodes.Ldfld, Infos.VirtualMachine.PhysicalMemory);

            registers.GenerateSegmentBaseValue("DS", this.SegmentOverride, il);
            registers.GenerateValue(this.AddressSize == 32 ? "ESI" : "SI", il);
            il.Emit(OpCodes.Conv_U4);
            il.Emit(OpCodes.Add);

            GenerateGetMemoryValue();
        }
        /// <summary>
        /// Generates IL to get or set the value at ES:(E)SI.
        /// </summary>
        /// <param name="generateSetValue">Method to generate the source value in IL; null to get ES:(E)DI.</param>
        protected void GenerateES_DI(Action generateSetValue)
        {
            il.LoadArgument(0);
            il.Emit(OpCodes.Ldfld, Infos.VirtualMachine.PhysicalMemory);

            registers.GenerateSegmentBaseValue("ES", il);
            registers.GenerateValue(this.AddressSize == 32 ? "EDI" : "DI", il);
            il.Emit(OpCodes.Conv_U4);
            il.Emit(OpCodes.Add);

            if(generateSetValue == null)
                GenerateGetMemoryValue();
            else
            {
                generateSetValue();

                if(this.OperandSize == 32)
                    il.Emit(OpCodes.Call, Infos.PhysicalMemory.SetUInt32);
                else if(this.OperandSize == 16)
                    il.Emit(OpCodes.Call, Infos.PhysicalMemory.SetUInt16);
                else if(this.OperandSize == 8)
                    il.Emit(OpCodes.Call, Infos.PhysicalMemory.SetByte);
                else
                    throw new InvalidOperationException();
            }
        }
        /// <summary>
        /// Generates IL to get or set the value in AL/(E)AX.
        /// </summary>
        /// <param name="generateSetValue">Method to generate the source value in IL; null to get AL/(E)AX.</param>
        protected void GenerateAX(Action generateSetValue)
        {
            if(generateSetValue == null)
            {
                if(this.OperandSize == 32)
                    registers.GenerateValue("EAX", il);
                else if(this.OperandSize == 16)
                    registers.GenerateValue("AX", il);
                else if(this.OperandSize == 8)
                    registers.GenerateValue("AL", il);
                else
                    throw new InvalidOperationException();
            }
            else
            {
                registers.GenerateReference("AX", il);
                generateSetValue();
                if(this.OperandSize == 32)
                    il.Emit(OpCodes.Stind_I4);
                else if(this.OperandSize == 16)
                    il.Emit(OpCodes.Stind_I2);
                else if(this.OperandSize == 8)
                    il.Emit(OpCodes.Stind_I1);
                else
                    throw new InvalidOperationException();
            }
        }
        /// <summary>
        /// Generates IL to get the value of DX.
        /// </summary>
        protected void GenerateDX()
        {
            registers.GenerateValue("DX", il);
        }
        /// <summary>
        /// Generates IL to increment/decrement (E)SI.
        /// </summary>
        protected void IncrementSI()
        {
            registers.GenerateReference("SI", il);
            registers.GenerateValue(this.AddressSize == 32 ? "ESI" : "SI", il);
            GenerateIncrementAdd();
        }
        /// <summary>
        /// Generates IL to increment/decrement (E)DI.
        /// </summary>
        protected void IncrementDI()
        {
            registers.GenerateReference("DI", il);
            registers.GenerateValue(this.AddressSize == 32 ? "EDI" : "DI", il);
            GenerateIncrementAdd();
        }
        /// <summary>
        /// Generates IL to load the VirtualMachine instance onto the stack.
        /// </summary>
        protected void LoadVirtualMachine()
        {
            il.LoadArgument(0);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Generates IL to get a memory value.
        /// </summary>
        private void GenerateGetMemoryValue()
        {
            if(this.OperandSize == 32)
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetUInt32);
            else if(this.OperandSize == 16)
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetUInt16);
            else if(this.OperandSize == 8)
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetByte);
            else
                throw new InvalidOperationException();
        }
        /// <summary>
        /// Generates IL to add the incrementer to a pointer.
        /// </summary>
        private void GenerateIncrementAdd()
        {
            il.Emit(OpCodes.Conv_U4);
            il.Emit(OpCodes.Conv_I4);

            il.LoadLocal(this.context.GetIncrementer(il));
            il.Emit(OpCodes.Add);
            if(this.AddressSize == 32)
            {
                il.Emit(OpCodes.Conv_U4);
                il.Emit(OpCodes.Stind_I4);
            }
            else
            {
                il.Emit(OpCodes.Conv_U2);
                il.Emit(OpCodes.Stind_I2);
            }
        }
        /// <summary>
        /// Generates the IL for the instruction.
        /// </summary>
        private void GenerateCode()
        {
            var startLabel = il.DefineLabel();

            // incrementer = dir ? -(opsize / 8) : (opsize / 8) 
            var subLabel = il.DefineLabel();
            il.LoadArgument(0);
            il.Emit(OpCodes.Ldfld, Infos.VirtualMachine.Processor);
            il.Emit(OpCodes.Ldfld, Infos.Processor.Flags);
            il.LoadConstant((int)EFlags.Direction);
            il.Emit(OpCodes.And);
            il.Emit(OpCodes.Brtrue_S, subLabel);
            il.LoadConstant(this.OperandSize / 8);
            il.StoreLocal(this.context.GetIncrementer(il));
            il.Emit(OpCodes.Br_S, startLabel);
            il.MarkLabel(subLabel);
            il.LoadConstant(-(this.OperandSize / 8));
            il.StoreLocal(this.context.GetIncrementer(il));

            il.MarkLabel(startLabel);

            if(this.Repeat != RepeatPrefix.None)
            {
                var doneLabel = il.DefineLabel();

                var loopLabel = il.DefineLabel();

                // if (e)cx == 0 goto doneLabel
                registers.GenerateValue(this.AddressSize == 32 ? "ECX" : "CX", il);
                il.Emit(OpCodes.Brfalse, doneLabel);

                il.MarkLabel(loopLabel);

                GenerateIteration();

                // (e)cx = (e)cx - 1
                registers.GenerateReference("CX", il);
                registers.GenerateValue(this.AddressSize == 32 ? "ECX" : "CX", il);
                il.Emit(OpCodes.Conv_I4);
                il.LoadConstant(1);
                il.Emit(OpCodes.Sub);
                il.Emit(this.AddressSize == 32 ? OpCodes.Conv_U4 : OpCodes.Conv_U2);
                il.Emit(this.AddressSize == 32 ? OpCodes.Stind_I4 : OpCodes.Stind_I2);

                // Increment instruction counter
                il.LoadLocal(context.CounterInt);
                il.LoadConstant(1);
                il.Emit(OpCodes.Add);
                il.StoreLocal(context.CounterInt);

                // if (e)cx == 0 goto doneLabel
                registers.GenerateValue(this.AddressSize == 32 ? "ECX" : "CX", il);
                il.Emit(OpCodes.Brfalse, doneLabel);
                // if repeating and flag is set/not set goto doneLabel
                if(this.checkFlags)
                {
                    il.LoadArgument(0);
                    il.Emit(OpCodes.Ldfld, Infos.VirtualMachine.Processor);
                    il.Emit(OpCodes.Ldfld, Infos.Processor.Flags);
                    il.LoadConstant((int)EFlags.Zero);
                    il.Emit(OpCodes.And);
                    il.Emit(this.Repeat == RepeatPrefix.Repne ? OpCodes.Brtrue : OpCodes.Brfalse, doneLabel);
                }

                // Repeat instruction if instruction counter is small enough
                il.LoadLocal(context.CounterInt);
                il.LoadConstant(context.MaxCount);
                il.Emit(OpCodes.Clt);
                il.Emit(OpCodes.Brtrue, loopLabel);

                this.registers.GenerateReference("EIP", il);
                il.LoadConstant((int)context.Instruction.EIP);
                il.Emit(OpCodes.Stind_I4);
                il.LoadLocal(context.CounterInt);
                il.Emit(OpCodes.Ret);

                il.MarkLabel(doneLabel);
            }
            else
                GenerateIteration();
        }
        #endregion
    }
}
